/*
 * HashFile.cpp
 *
 *  Created on: Oct 18, 2012
 *      Author: Federico Orquera
 */

#include "HashFile.h"

#define _INITIAL_BUCKET_TD (HashBucket::TypeTD)1
#define _DISPERSION_TABLE_OFFSET 2+sizeof(size_t)

const size_t HashFile::minimumControlDataSize = 2 + sizeof(size_t) + sizeof(HashIndex);
const std::ios_base::openmode HashFile::blockFileDefaultOpenMode = std::ios_base::in | std::ios_base::out;
HashBucketSerializer HashFile::bucketSerializer;

HashFile::HashFile(const std::string &filename, size_t blockSize, bool forceTrunc ) :
        m_blockSize( blockSize ),
        m_hashFile( blockSize )
{
    bool hashFileAlreadyExists = true;

    // Acá tengo q detectar si el archivo existe o no
    // Si existe lo abro y verifico los datos de control, sino lo creo con datos de control válidos
    if ( forceTrunc )
    {
        hashFileAlreadyExists = false;
    }
    else
    {
        this->parseHeader( filename, hashFileAlreadyExists );
    }

    m_hashFile.resetBlockSize( m_blockSize );

    if ( hashFileAlreadyExists )
    {
        m_hashFile.open( filename.c_str(), blockFileDefaultOpenMode );
    }
    else
    {
        m_hashFile.open( filename.c_str(), blockFileDefaultOpenMode | std::ios_base::trunc );
        this->writeHeader();

        this->getNewBucket( _INITIAL_BUCKET_TD );
    }
}

void HashFile::parseHeader(const std::string &filename, bool &fileExists )
{
    std::ifstream inFile;
    size_t blockSize;
    char c;

    inFile.open( filename.c_str() );

    if ( inFile.fail() )
    {
        fileExists = false;
    }
    else
    {
        // validate header and auto-discover blockSize
        if( !( inFile >> c && c == 'H' && inFile >> c && c == 'F' && inFile.read( (char*)&blockSize, sizeof(size_t) ) ) )
            throw "Archivo Corrupto.";

        fileExists = true;
        m_blockSize = blockSize;

        assert( m_blockSize > minimumControlDataSize );

        inFile.close();
    }
}

void HashFile::writeHeader()
{
    std::unique_ptr< controlBlock_t > controlBlock;
    physical::byte* buffer;

    // TODO: lanzar una exception si esto no pasa:
    assert( m_blockSize > minimumControlDataSize );

    controlBlock.reset( new  physical::byte[m_blockSize] );
    buffer = (physical::byte*) controlBlock.get();

    // escribo Magic Number
    buffer[0] = 'H';
    buffer[1] = 'F';

    buffer += 2;

    // escribo tamaño de bloque (para verificar al abrir el archivo)
    memcpy( buffer, &m_blockSize, sizeof(size_t) );
    buffer += sizeof(size_t);

    // Escribo una tabla de dispersión vacia
    // Escribo el tamaño inicial de la tabla de dispersion ( tamaño inicial = 1 )
    HashIndex DispersionTableInitialSize = 1;
    memcpy( buffer, &DispersionTableInitialSize, sizeof(HashIndex) );
    buffer += sizeof(HashIndex);

    // La Inicializo con el valor 1
    HashBucket::BucketID inicialHashBucketId = 1;
    memcpy( buffer, &inicialHashBucketId, sizeof(HashBucket::BucketID) );

    m_hashFile.writeBlock( _CONTROL_BLOCK_NBR, controlBlock.get() );
}

HashFile::~HashFile()
{
}

bucket_ptr HashFile::getNewBucket( HashBucket::TypeTD td )
{
    // Escribo un bloque nuevo, obtengo su nº y creo un bucket con dicho ID
    physical::blockNumber newBlockId;
    newBlockId = m_hashFile.getBlocksCount();
    bucket_ptr newBucket( new HashBucket( m_blockSize, newBlockId, td ) );

    this->saveBucket( newBucket );

    return newBucket;
}

bucket_ptr HashFile::getBucket( HashBucket::BucketID bucketNr )
{
    physical::UP_byteStream buffer;
    bucket_ptr bucket;

    assert( bucketNr > 0 );

    buffer.reset( new physical::byte[ m_blockSize ] );

    // Leo el bloque que contiene el bucket
    m_hashFile.readBlock( bucketNr, buffer.get() );

    // Verifico que dicho bloque no esté libre
    // TODO: por el momento no se hace porque en el hash no hay liberación de bloques.

    // Parseo el bucket del bloque y lo devuelvo
    bucketSerializer.deserialize( buffer.get(), bucket );

    bucket->setBucketNumber( bucketNr );
    bucket->setMaxCapacity( m_blockSize );

    return bucket;
}

void HashFile::saveBucket( bucket_ptr& bucket )
{
    assert( bucket->getBucketNr() > 0 );

    physical::UP_byteStream buffer( new physical::byte[m_blockSize] );
    bucketSerializer.serialize( bucket, buffer.get() );
    m_hashFile.writeBlock( bucket->getBucketNr(), buffer.get() );
}

physical::UP_byteStream HashFile::getControlBlock()
{
    physical::UP_byteStream buffer( new physical::byte[m_blockSize] );
    m_hashFile.readBlock( _CONTROL_BLOCK_NBR, buffer.get() );
    return buffer;
}

void HashFile::writeControlBlock( physical::UP_byteStream controlBlock )
{
    m_hashFile.writeBlock( _CONTROL_BLOCK_NBR, controlBlock.get() );
}

void HashFile::getDispersionTable( HashBucket::TypeTD* &table, HashIndex &tableSize )
{
    physical::byte* serializedDispersionTable;
    physical::UP_byteStream controlBlock = this->getControlBlock();

    serializedDispersionTable = controlBlock.get() + _DISPERSION_TABLE_OFFSET;
    memcpy( &tableSize, serializedDispersionTable, sizeof(HashIndex) );
    serializedDispersionTable += sizeof(HashIndex);

    table = new HashBucket::TypeTD[tableSize];

    memcpy( table, serializedDispersionTable, tableSize*sizeof(HashBucket::TypeTD) );
}

void HashFile::saveDispersionTable( HashBucket::TypeTD *table, HashIndex tableSize )
{
    physical::byte* serializedDispersionTable;
    physical::UP_byteStream controlBlock = this->getControlBlock();

    std::cerr << "Grabando Tabla de dispersion: "<< std::endl
    		<< "tamaño: "<< tableSize << std::endl;

    assert( ( tableSize * sizeof(HashBucket::TypeTD) + sizeof(HashIndex) + _DISPERSION_TABLE_OFFSET ) <= m_blockSize );

    serializedDispersionTable = controlBlock.get() + _DISPERSION_TABLE_OFFSET;

    memcpy( serializedDispersionTable, &tableSize, sizeof(HashIndex) );
    serializedDispersionTable += sizeof(HashIndex);

    //table = new HashBucket::TypeTD[tableSize];

    memcpy( serializedDispersionTable, table, tableSize*sizeof(HashBucket::TypeTD) );

    this->writeControlBlock( std::move( controlBlock ) );
}
